package de.uni_potsdam.hpi.bpt.ai.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 * Calculates certain aggregates over a set of collected values for experimental
 * evaluation of any kind.  
 * 
 * @author Matthias Kunze <mtkunze@gmail.com>
 *
 * @param <T> the type of aggregated values, must be a java.lang.Number
 */
public class Aggregate<T extends Number> extends ArrayList<T> implements List<T>, Serializable {

	private static final long serialVersionUID = -9128724712173041680L;
	protected final static Comparator<Number> Comp = new Comparator<Number>() {

		@Override
		public int compare(Number o1, Number o2) {
			return Double.compare(o1.doubleValue(), o2.doubleValue());
		}
	};

	
	public Aggregate() {
		super();
	}
//
//	@Override
//	public boolean add(T t) {
//		if (Double.isNaN(t.doubleValue())) {
//			System.out.print("NaN");
//			return false;
//		}
//		
//		return super.add(t);
//	}
//	
//	@Override
//	public boolean addAll(Collection<? extends T> collection) {
//		for (T t : collection) {
//			this.add(t);
//		}
//		
//		return true;
//	}
//	
	
	/**
	 * Get the minimum of all collected values
	 * @return
	 */
	public T min() {
		Collections.sort(this, Comp);
		return this.get(0);
	}
	
	/**
	 * Get the maximum of all collected values
	 * @return
	 */
	public T max() {
		Collections.sort(this, Comp);
		return this.get(this.size()-1);
	}
	
	/**
	 * Get the average value of all collected values
	 * @return
	 */
	public double avg() {
		return this.sum() / (double)this.size();
	}
	
	/**
	 * Get the sum over all elements.
	 * 
	 * @return
	 */
	public  double sum() {
		
		double sum = 0;
		for (T value : this) {
			sum += value.doubleValue();
		}
		return sum;
	}
	
	/**
	 * Get the median value of all collected values, i.e., the value of which 
	 * there exist equally many elements that are lesser as such that are 
	 * greater.
	 * If no such value exists, the average of the adjacent values will be
	 * calculated.
	 *  
	 * @return
	 */
	public double median() {
		return this.quantile(0.5);
	}
	
	/**
	 * get the p-quantile of all collected values, i.e., the value of which 
	 * there exist (p*100)% elements that are lesser and ((1-p*100))% elements
	 * that are greater.
	 * If no such value exists, the p-quantile average of the adjacent values 
	 * will be calculated.
	 *  
	 * @param p the quantile fraction, must be 0 <= p <= 1
	 * @return
	 */
	public double quantile(double p) {
		if (0 >= p) {
			return this.min().doubleValue();
		}
		
		if (1 <= p) {
			return this.max().doubleValue();
		}
		
		// 0.3-quantile contains 30% of all values
		
		Collections.sort(this, Comp);
		int size = this.size();
		double pos = size * p; 
		Number[] sd = this.toArray(new Number[size]);
		
		if ((int)p == p) {
			return sd[(int)pos].doubleValue();
		}
		else {
			return p*sd[(int)pos].doubleValue() + (1-p)*sd[Math.min((int)pos + 1, size-1)].doubleValue();
		}
	}
	
	/**
	 * Puts data in a map, where the key is each unique value in this collection
	 * and the value is the number of occurrences of this value.
	 * 
	 * @return
	 */
	public Map<T, Integer> cluster() {
		Map<T, Integer> cluster = new HashMap<T, Integer>();
		for (T i : this) {
			if (!cluster.containsKey(i)) {
				cluster.put(i, 1);
			}
			else {
				cluster.put(i, cluster.get(i) + 1);
			}
		}
		return cluster;
	}
	
	/**
	 * Rounds a double with precision 0.000.
	 * 
	 * @param d
	 * @return
	 */
	public static double r(double d) {
		return Math.round(d*1000.0)/1000.0;
	}
		
	/**
	 * A simple example.
	 * 
	 * @param margs
	 */
	public static void main(String[] margs) {
		Aggregate<Integer> a = new Aggregate<Integer>();
		
		for (int i=1; i<=90;i++) {
			a.add(i);
		}
		
		System.out.println("min: " + a.min());
		System.out.println("max: " + a.max());
		System.out.println("avg: " + a.avg());
		System.out.println("median: " + a.median());
		System.out.println("quartile: " + a.quantile(0.25));
		System.out.println("quantile(0.9): " + a.quantile(0.9));
	}
	
}
